package org.superheroes.net;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Random;
import java.util.StringTokenizer;
import java.util.Timer;
import java.util.TimerTask;

import org.superheroes.net.packet.RTPPacket;
import org.superheroes.net.stream.VideoStream;

import android.util.Log;

/**
 * 
 * @author Jeffrey Goudzwaard
 * @author Davin Terrizzi
 * @version 1.0
 */
public class ClientStreamThread extends Thread
{
	private static final String LOG_TAG = ClientStreamThread.class.getName();
	private static final String LOG_ERROR_TAG = ClientStreamThread.class.getName() + ".error";
	
	private VideoStream videoStream;
	
	private int imageNr = 0;
	private int clientPort = 0;
	private int state;
	private int sessionId = getRandomSessionId();
	private int seqNr = 0;
	
	private DatagramSocket udpSocket;
	
	private Socket socket;
	
	private DatagramPacket udpPacket;
	
	private InetAddress clientIP;
	
	private BufferedWriter writer;
	
	private BufferedReader reader;
	
	private byte[] buffer;
	
	private String videoFileName;
	
	private Timer timer;
	
	public ClientStreamThread( Socket clientSocket )
	{
		this.socket = clientSocket;
		
		init();
	}
	
	/**
	 * Initializes the client stream thread
	 */
	private void init()
	{
		timer = new Timer();
		
		buffer = new byte[ 15000 ];
		
		Log.i( LOG_TAG, "Client found: ("  + socket.getRemoteSocketAddress() + ")" );
		
		try
		{
			clientIP = InetAddress.getByName( "10.0.2.2" );
		}
		catch( UnknownHostException e )
		{
			Log.i( LOG_ERROR_TAG, e.toString() );
		}
		
		state = StreamingServer.INIT;
		
		try
		{
			reader = new BufferedReader( new InputStreamReader( socket.getInputStream() ) );
			writer = new BufferedWriter( new OutputStreamWriter( socket.getOutputStream() ) );
		}
		catch( IOException e )
		{
			Log.e( LOG_ERROR_TAG, e.toString() );
		}
	}
	
	/**
	 * return a random session id
	 * @return randomSessionId
	 */
	private int getRandomSessionId()
	{
		return new Random().nextInt( 150000 );
	}
	
	public void run()
	{
		int requestType = -1;
		boolean done = false;
		while( !done )
		{
			requestType = parseRequest();
			Log.i( LOG_TAG, "Request type recieved: " + requestType );
			
			if( requestType == StreamingServer.SETUP )
			{
				done = true;
				
				state = StreamingServer.READY;
				Log.i( LOG_TAG, "New State: READY" );
				
				sendResponse();
				
				try
				{
					videoStream = new VideoStream( videoFileName );
					udpSocket = new DatagramSocket();
				}
				catch( SocketException e )
				{
					Log.e( LOG_ERROR_TAG, e.toString() );
				}
				catch( Exception e )
				{
					Log.e( LOG_ERROR_TAG, e.toString() );
				}
			}
		}
		
		while( true )
		{
			requestType = parseRequest();
			
			if( ( requestType == StreamingServer.PLAY ) && ( state == StreamingServer.READY ) )
			{
				sendResponse();
				timer.schedule( new PerformTimerAction(), 0, StreamingServer.FRAME_PERIOD );
				
				state = StreamingServer.PLAYING;
				Log.i( LOG_TAG, "New state: PLAYING" );
			}
			else if( ( requestType == StreamingServer.PAUSE ) && ( state == StreamingServer.PLAYING ) )
			{
				sendResponse();
				timer.cancel();
				state = StreamingServer.READY;
				Log.i( LOG_TAG, "New state: READY" );
			}
			else if( requestType == StreamingServer.TEARDOWN )
			{
				sendResponse();
				timer.cancel();
				try
				{
					udpSocket.close();
					socket.close();
				}
				catch( SocketException e )
				{
					Log.e( LOG_ERROR_TAG, e.toString() );
				}
				catch( IOException e )
				{
					Log.e( LOG_ERROR_TAG, e.toString() );
				}
			}
		}
	}
	
	/**
	 * Sends a response to the client
	 */
	private void sendResponse()
	{
		try
		{
			writer.write( "RTSP/1.0 200 OK\r\n" );
			writer.write( "CSeq: " + seqNr + "\r\n" );
			writer.write( "Session: " + sessionId + "\r\n" );
			writer.flush();
		}
		catch( IOException e )
		{
			Log.e( LOG_ERROR_TAG, e.toString() );
		}
	}
	
	/**
	 * Parses the client request
	 * @return requestType
	 */
	private int parseRequest()
	{
		int requestType = -1;
		
		try
		{
			String requestLine = reader.readLine();
			
			if( requestLine != null )
			{
				Log.i( LOG_TAG, " Received requestLine: " + requestLine );
				
				StringTokenizer tokens = new StringTokenizer( requestLine );
				String requestTypeString = tokens.nextToken();
				
				if( requestTypeString.equals( "SETUP" ) )
				{
					requestType = StreamingServer.SETUP;
				}
				else if( requestTypeString.equals( "PLAY" ) )
				{
					requestType = StreamingServer.PLAY;
				}
				else if( requestTypeString.equals( "PAUSE" ) )
				{
					requestType = StreamingServer.PAUSE;
				}
				else if( requestTypeString.equals( "TEARDOWN" ) )
				{
					requestType = StreamingServer.TEARDOWN;
				}
				
				if( requestType == StreamingServer.SETUP )
				{
					videoFileName = tokens.nextToken();
				}
				
				String seqNr = reader.readLine();
				Log.i( LOG_TAG, "Sec nr: " + seqNr );
				
				tokens = new StringTokenizer( seqNr );
				tokens.nextToken();
				this.seqNr = Integer.parseInt( tokens.nextToken() );
				
				String lastLine = reader.readLine();
				Log.i( LOG_TAG, lastLine );
				
				if( requestType == StreamingServer.SETUP )
				{
					tokens = new StringTokenizer( lastLine );
					for( int i = 0; i < 3; i++ )
					{
						tokens.nextToken();
					}
					clientPort = Integer.parseInt( tokens.nextToken() );
				}
			}
			else
			{
				//Log.e( LOG_ERROR_TAG, "Request line is null" );
			}
		}
		catch( Exception e )
		{
			Log.e( LOG_ERROR_TAG, e.toString() );
		}
		
		return requestType;
	}
	
	/**
	 * 
	 * @author Jeffrey Goudzwaard
	 * @author Davin Terrizzi
	 * @version 1.0
	 */
	public class PerformTimerAction extends TimerTask
	{
		@Override
		public void run()
		{
			if( imageNr < StreamingServer.VIDEO_LENGTH )
			{
				imageNr++;
				
				try
				{
					int imageLength = videoStream.getNextFrame( buffer );
					
					RTPPacket packet = new RTPPacket( StreamingServer.MJPEG_TYPE, imageNr, imageNr * StreamingServer.FRAME_PERIOD, buffer, imageLength );
					
					int packetLenght = packet.getLenght();
					
					byte[] bits = new byte[ packetLenght ];
					packet.getPacket( bits );
					
					udpPacket = new DatagramPacket( bits, packetLenght, clientIP, clientPort );
					udpSocket.send( udpPacket );
					
					packet.printHeader();
				}
				catch( Exception e )
				{
					Log.e( LOG_ERROR_TAG, e.toString() );
				}
			}
			else
			{
				timer.cancel();
			}
		}
	}
}
